package tech.yixiyun.framework.kuafu.bean;


import tech.yixiyun.framework.kuafu.bean.annotation.NotInject;
import tech.yixiyun.framework.kuafu.kits.ObjectKit;
import tech.yixiyun.framework.kuafu.log.LOGGER;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

public class BeanContext {

    /**
     * Bean类定义库
     */
    private static final ConcurrentHashMap<Class, BeanDefinition> beanLib = new ConcurrentHashMap<>();

    /**
     * 类的实例上需要注入的字段列表
     */
    private static final ConcurrentHashMap<Class, List<Field>> fieldLib = new ConcurrentHashMap<>();





    /**
     * 注册Bean类，同一个Bean类是不允许重复注册的
     * @param beanClass Bean原始类
     * @param beanDefinition 解析获得的类定义
     */
    public static BeanDefinition register(Class beanClass, BeanDefinition beanDefinition) {
        //Bean类必须有无参构造器
        try {
            if (beanClass.getConstructor() == null) {
                throw new BeanException(beanClass.getName() + "没有定义无参构造器，无法注册为Bean");
            }
        } catch (NoSuchMethodException e) {
            throw new BeanException(beanClass.getName() + "没有定义无参构造器，无法注册为Bean");
        }
        BeanDefinition definition = beanLib.putIfAbsent(beanClass, beanDefinition);
        if (definition != null) {
            throw new BeanException(beanClass.getName() + "已在BeanContext中注册过，无法重复注册");
        }
        LOGGER.trace("Bean【" + beanClass.getName()+"】注册成功");
        return beanDefinition;


    }

    /**
     * 获取Bean类的定义
     * @param beanClass
     * @return
     */
    public static BeanDefinition getBeanDefinition(Class beanClass) {
        return beanLib.get(beanClass);
    }

    /**
     * 获取Bean类的定义
     * @param beanClassName
     * @return
     */
    public static BeanDefinition getBeanDefinition(String beanClassName) {
        AtomicReference<BeanDefinition> rs = new AtomicReference<>();
        beanLib.keys().asIterator().forEachRemaining(item -> {
            if (item.getName().equals(beanClassName)) {
                rs.set(beanLib.get(item));
            }
        });

        return rs.get();
    }


    /**
     * 获取所有的BeanDefinition
     * @return
     */
    public static BeanDefinition[] getAllBeanDefinitions() {
        return beanLib.values().toArray(new BeanDefinition[0]);
    }



    /**
     * 获取Bean实例
     * @param originalClass
     * @return
     */
    public static <T> T getBean(Class<T> originalClass) {
        BeanDefinition definition = getBeanDefinition(originalClass);
        if (definition == null) {
            throw new BeanException(originalClass.getName() + "未注册为Bean，无法获取它的实例");
        }

        return ObjectKit.ifNotNull(definition.getBeanMode(), mode -> mode.getInstance(definition, false));



    }



    /**
     * 依赖注入
     * @param instance
     * @param clazz
     * @param <T>
     */
    public static <T> void inject(T instance, Class<T> clazz) {
        Class<? super T> superClass = clazz.getSuperclass();
        if (superClass != Object.class) {
            inject(instance, superClass);
        }
        List<Field> needInjectFields = fieldLib.get(clazz);
        if (needInjectFields == null) {
            needInjectFields = new ArrayList<>();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                int modifiers = field.getModifiers();
                if (Modifier.isStatic(modifiers) ==false && Modifier.isFinal(modifiers) ==false
                    && needInject(field)
                ) {
                    field.setAccessible(true);
                    needInjectFields.add(field);
                }
            }
            List<Field> temp = fieldLib.putIfAbsent(clazz, needInjectFields.isEmpty() ? Collections.emptyList(): needInjectFields);
            if (temp != null) {
                needInjectFields = temp;
            }
        }

        for (Field field : needInjectFields) {
            try {
                if (field.get(instance) == null) {
                    field.set(instance, getToInjectedBean(field.getType()));
                }
            } catch (Exception e) {
                throw new BeanException("为"+clazz+"实例的"+field.getName()+"字段赋值时发生异常", e);
            }
        }
    }

    /**
     * 获取将要用来注入的Bean实例
     * @param originalClass
     * @return
     */
    private static <T> T getToInjectedBean(Class<T> originalClass) {
        BeanDefinition definition = getBeanDefinition(originalClass);
        if (definition == null) {
            throw new BeanException(originalClass.getName() + "未注册为Bean，无法获取它的实例");
        }


        return definition.getBeanMode().getInstance(definition, true);
    }

    /**
     * 判断一个字段是否需要注入
     * @param field
     * @return
     */
    private static boolean needInject(Field field) {
        if (field.isAnnotationPresent(NotInject.class)) {
            return false;
        }
        return beanLib.containsKey(field.getType());
    }

    /**
     * 检测某个类的单例依赖是否都OK
     * @param originClass
     * @return
     */
    protected static boolean checkDependencyIsAllOk(Class originClass) {
        //检查它的依赖是否状态都over，如果是，就over，不是继续下次检查
        List<Field> fields = fieldLib.get(originClass);
        boolean allOver = true;
        for (Field field : fields) {
            BeanDefinition beanDefinition = getBeanDefinition(field.getType());
            //检测到依赖已经ready了，直接更新它为over
            if (beanDefinition.setState(BeanState.OVER) == false) {
                allOver = false;
            }
        }

        return allOver;
    }





    /**
     * 移除某个Bean类
     * @param clazz
     */
    public static void unRegister(Class clazz) {
        LOGGER.warn("即将注销Bean类{}", clazz);
        beanLib.remove(clazz);
        fieldLib.remove(clazz);
    }





    /**
     * 更新某个类的依赖关系，框架用的，不要随便私自调用
     * @param dependedClass
     * @return
     */
    public static void updateDependClasses(Class dependedClass) {
        if (beanLib.containsKey(dependedClass) == false) return;
        fieldLib.remove(dependedClass);
        List<Field> needInjectFields  = new ArrayList<>();
        Field[] fields = dependedClass.getDeclaredFields();
        for (Field field : fields) {
            int modifiers = field.getModifiers();
            if (Modifier.isStatic(modifiers) ==false && Modifier.isFinal(modifiers) ==false
                    && needInject(field)
            ) {
                field.setAccessible(true);
                needInjectFields.add(field);
            }
        }
        List<Field> temp = fieldLib.putIfAbsent(dependedClass, needInjectFields.isEmpty() ? Collections.emptyList(): needInjectFields);


    }




}
